﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MyFloridaHouse.libSvOpenDistricts.Districts;
using MyFloridaHouse.libSvUtilities.Logging;
using MyFloridaHouse.ServiceConn.DistrBldrSvcRef;
using MyFloridaHouse.libSvOpenDistricts.DataService;
using MyFloridaHouse.libSvCommonUtilities;
using MyFloridaHouse.libSvUtilities;
using System.ComponentModel;
using MyFloridaHouse.libSvOpenDistricts.StorageIsolated;
using System.Windows;

namespace MyFloridaHouse.libSvOpenDistricts.Census
{
    public delegate void CensusGeometryInitializedEventHandler();

    public partial class CensusGeometry : INotifyPropertyChanged
    {

        #region events
        public static event CensusGeometryInitializedEventHandler CensusGeometryInitialized;

        private static void RaiseCensusGeometryInitialized(object sender)
        {
            if (CensusGeometryInitialized != null)
            {
                CensusGeometry._censusGeometryInstance.isInitializeComplete = true;
                CensusGeometryInitialized();
            }
            else
            {
                // todo: Log this error
                throw (new NotImplementedException("No Subscribers to CensusGeometryInitialized Event"));
            }
        }

        #endregion

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion


        private bool _isInitializeComplete = false;
        public bool isInitializeComplete
        {
            get { return _isInitializeComplete; }
            set { if (_isInitializeComplete != value) { _isInitializeComplete = value; NotifyPropertyChanged("isInitializeComplete"); } }
        }

        private bool _isInitializeError = false;
        public bool isInitializeError
        {
            get { return _isInitializeError; }
            set { if (_isInitializeError != value) { _isInitializeError = value; NotifyPropertyChanged("isInitializeError"); } }
        }

        #region   censusGeometryInstance - outside interface through static class
        private static CensusGeometry _censusGeometryInstance;
        public static CensusGeometry censusGeometryInstance
        {
            get { return _censusGeometryInstance; }
        }

        #endregion


        private int _blocksRemaining = 0;
        public int blocksRemaining
        {
            get
            {
                return _blocksRemaining;
            }
            set
            {
                if (_blocksRemaining != value)
                {
                    _blocksRemaining = value;
                    NotifyPropertyChanged("blocksRemaining");
                }
            }
        }

        private int _splitVTDs = 0;
        public int splitVTDs
        {
            get
            {
                return _splitVTDs;
            }
            set
            {
                if (_splitVTDs != value)
                {
                    _splitVTDs = value;
                    NotifyPropertyChanged("splitVTDs");
                }
            }
        }

        public List<CensusGeometryNode> nodeList { get { return censusGeometryNodeList; } }

        public List<CensusGeometryNode> censusGeometryNodeList {get; set; }

        public CensusGeometryHistory history { get; set; }

        private int maxDistrictCount { get; set; }

        public CensusGeometry(ServiceDAL serviceDAL)
        {
            // when the object is created, store a reference to it in the static class
            // That way anything that needs to access the instance methods can do so without passing around references all over the place
            _censusGeometryInstance = this;

            ServiceDAL.GetCensusGeometryCompleted += OnServiceDALGetCensusGeometryCompleted;
            serviceDAL.GetCensusGeometry();
        }


        private void OnServiceDALGetCensusGeometryCompleted(CensusGeometrySTO sto)
        {
            bool saveToIsolatedRequired = false;
            try
            {
                if (sto.packedDataList.Length > 0)
                {
                    CensusGeometryListDTO censusGeometryDeserializeList = new CensusGeometryListDTO();
                    try
                    {
                        byte[] serializedData = CompressionManager.DecompressToByteArray(sto.packedDataList);
                        censusGeometryDeserializeList = (CensusGeometryListDTO)SerializationManager.Deserialize(typeof(CensusGeometryListDTO), serializedData);
                        saveToIsolatedRequired = true;
                    }
                    catch(Exception ex)
                    {
                        throw new Exception ("Failed to unpack data from server : " + ex.Message);
                    }
                    try
                    {
                        // load up the data
                        CreateRootNode();
                        AddCensusGeometry(censusGeometryDeserializeList.censusGeometryList.ToList());

                    }
                    catch (Exception ex)
                    {
                        throw new Exception("Failed to build census geometry list : " + ex.Message);
                    }

                    if (saveToIsolatedRequired)
                    {

                        try
                        {
                            // save for quick load next time
                            IsolatedStorageHelper.DeleteCensusGeometry();
                            IsolatedStorageHelper.SaveCensusGeometry(censusGeometryNodeList);
                        }
                        catch (Exception ex)
                        {
                            Log.RecordNonFatalError("Unable to save census geometry to isolated storage for future quick start (you may continue using application) : " + ex.Message);
                        }
                    }
                    
                }
                else
                {
                    try
                    {
                        // perform quick load
                        censusGeometryNodeList = new List<CensusGeometryNode>();
                        censusGeometryNodeList.AddRange(IsolatedStorageHelper.LoadCensusGeometry());
                        DetermineChildNodeLists();
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("Failed to load census geometry from isolated storage : " + ex.Message);
                    }
                }
                if (censusGeometryNodeList.Count() != sto.rowCount)
                {
                    throw new Exception("Number of records loaded [" + censusGeometryNodeList.Count() + "] doesn't match expected size [" + sto.rowCount.ToString()  + "]");
                }
            }
            catch (Exception ex)
            {
                isInitializeError = true;

                // for any load error, delete the file - this will force the system to get the file from the cloud at next startup
                try
                {
                    IsolatedStorageHelper.DeleteCensusGeometry();
                }
                catch
                { 
                    Log.HandleInternalError("There was an error loading the Census Geometry.  Please close all browsers and restart :" + Environment.NewLine + ex.Message);
                }
            }

            CheckPlanComplete();

            CensusGeometry.RaiseCensusGeometryInitialized(this);
        }


        private void CreateRootNode()
        {
            censusGeometryNodeList = new List<CensusGeometryNode>();
            // create a root node in the plan (this represents the State)
            // it also shifts the items by one in the list, since uogid starts counting at 1
            CensusGeometryDTO rootNode = new CensusGeometryDTO();
            rootNode.uogID = 0;
            rootNode.uogType = CensusGeometryConstants.censusRootNodeUogType;
            rootNode.parentUogID = CensusGeometryConstants.noParentNode;
            CensusGeometryNode rootUog = new CensusGeometryNode(rootNode);
            censusGeometryNodeList.Add(rootUog);
        }

        private void AddCensusGeometry(List<CensusGeometryDTO> uogList)
        {
            // create a tree node for all items in the geometry list
            foreach (var uog in uogList)
            {
                censusGeometryNodeList.Add(new CensusGeometryNode(uog));
            }

            DetermineChildNodeLists();
        }

        private void DetermineChildNodeLists()
        {
            // initialize each
            foreach (var node in censusGeometryNodeList)
            {
                node.childNodeList = new List<CensusGeometryNode>();
            }

            // determine all the children of each node
            foreach (var node in censusGeometryNodeList)
            {
                if ((node.parentuogID >= 0) & (node.parentuogID < censusGeometryNodeList.Count()))
                {
                    censusGeometryNodeList[node.parentuogID].childNodeList.Add(node);
                }
            }
        }

        private void CheckPlanComplete()
        {
            blocksRemaining = (from node in censusGeometryNodeList
                    where node.districtID == CensusGeometryConstants.unassignedDistrictID
                             & node.uogType == CensusGeometryConstants.censusBlockUogType
                               select node.districtID).Count();

            splitVTDs = (from node in censusGeometryNodeList
                         where node.districtID == CensusGeometryConstants.multiDistrictID
                             & node.uogType == CensusGeometryConstants.censusVTDUogType
                         select node.districtID).Count();

        }

        public void SetLockByDistrict(int districtID, bool newLockValue)
        {
            var districtNodesQuery = from dist in censusGeometryNodeList
                                     where dist.districtID == districtID
                                     select dist;

            SetNodesLock(districtNodesQuery.ToList(), newLockValue);
        }

        public void SetDistrictLockAll(bool newLockValue)
        {
            SetNodesLock(censusGeometryNodeList, newLockValue);
        }

        private void SetNodesLock(IEnumerable<CensusGeometryNode> nodeLockList, bool newLockValue)
        {
            foreach (var censusNode in nodeLockList)
            {
                if ((censusNode.districtID != CensusGeometryConstants.unassignedDistrictID)
                   & (censusNode.districtID != CensusGeometryConstants.multiDistrictID))
                {
                    censusNode.isLocked = newLockValue;
                }
            }
        }

        public void ResetDistrictAll(int districtCount)
        {
            maxDistrictCount = districtCount;
            for (int ndx = 0; ndx < censusGeometryNodeList.Count; ndx++)
            {
                censusGeometryNodeList[ndx].districtID = CensusGeometryConstants.unassignedDistrictID;
                censusGeometryNodeList[ndx].isLocked = false;
            }
            ResetCensusGeometryHistory();
        }

        private void ResetCensusGeometryHistory()
        {
            if (history == null)
            {
                history = new CensusGeometryHistory();
                // todo: Need to re-look at how history notifies UI that history is available
                //history.PropertyChanged += new PropertyChangedEventHandler(MemberPropertyChanged);
            }
            else
            {
                history.Reset();
                CheckPlanComplete();
            }
        }

        internal string ScrubUogIDList(List<UOG> uogIDList)
        {
            List<int> badUogIDList = new List<int>();
            var badUogIDQUery =  from id in uogIDList
                                 where ((id.uogID < 0) & (id.uogID > censusGeometryNodeList.Count()))
                                 select id.regionID;
            return string.Join(",", badUogIDList);
        }


        public void AssignBlockOnlyUogListToDistrict(int newDistrict, List<int> blockOnlyUogIDList)
        {
            int nonBlockCount = (from uogID in blockOnlyUogIDList
                                join node in censusGeometryNodeList
                                on uogID equals  node.uogID
                                where node.uogType != 0
                                select node.uogID).Count();
            if (nonBlockCount > 0)
            {
                throw new Exception("Unable to make district assignment, could not reduce all selected items to census blocks");
            }

            foreach (var uogID in blockOnlyUogIDList)
            {

                if ((uogID < 0) | (uogID >= censusGeometryNodeList.Count()))
                {
                    throw new Exception("Unable to make district assignment, attempted to use an invalid uogID [" + uogID.ToString() + "]");
                }
                else
                {
                    if (!censusGeometryNodeList[uogID].isLocked)
                    {

                        int oldDistrictID = censusGeometryNodeList[uogID].districtID;
                        if (oldDistrictID != newDistrict)
                        {
                            censusGeometryNodeList[uogID].districtID = newDistrict;
                            history.SaveChange(oldDistrictID, newDistrict, uogID);
                        }
                    }
                }
            }
            UpdateNodeListAfterBypass(CensusGeometryConstants.censusBlockUogType);
            CheckPlanComplete();
        }

        private UogDistrictStruct GetCensusNodeChildrenDistricts(int parentIndex)
        {
            UogDistrictStruct censusNodeChildren = new UogDistrictStruct();

            var distinctUogDistrictQUery = (from node in censusGeometryNodeList[parentIndex].childNodeList
                                            select node.districtID).Distinct();


            if (censusGeometryNodeList[parentIndex].childNodeList.Count() == 0)
            {
                censusNodeChildren.hasChildren = false;
                censusNodeChildren.allChildrenInSameDistrict = true;  // even if there are no children, they are all in the same district

                // these should never be used if hasChildren is false
                censusNodeChildren.allChildrenDistrictID = CensusGeometryConstants.multiDistrictID;
            }
            else if (distinctUogDistrictQUery.Count() == 1)
            {
                censusNodeChildren.hasChildren = true;
                censusNodeChildren.allChildrenInSameDistrict = true;
                censusNodeChildren.allChildrenDistrictID = censusGeometryNodeList[parentIndex].childNodeList[0].districtID;
            }
            else
            {
                censusNodeChildren.hasChildren = true;
                censusNodeChildren.allChildrenInSameDistrict = false;

                // this should never be used if allChildrenInSameDistrict is false
                censusNodeChildren.allChildrenDistrictID = CensusGeometryConstants.multiDistrictID;
            }

            return censusNodeChildren;
        }


        public List<CensusGeometryNode> GetCensusBlocksFromUogIDList(List<UOG> uogIDList)
        {
            List<CensusGeometryNode> blockList = new List<CensusGeometryNode>();

            foreach (var sourceUogIDList in uogIDList)
            {
                var censusBlockNodeList = GetCensusBlocksFromUogID(sourceUogIDList.uogID);
                blockList.AddRange(censusBlockNodeList);
            }
            return blockList;
        }

        private List<CensusGeometryNode> GetCensusBlocksFromUogID(int uogID)
        {
            List<CensusGeometryNode> childBlockList = new List<CensusGeometryNode>();

            if (CensusGeometryConstants.censusBlockUogType == censusGeometryNodeList[uogID].uogType)
            {
                CensusGeometryNode newNode = new CensusGeometryNode();
                newNode.uogID = uogID;
                newNode.isLocked = censusGeometryNodeList[uogID].isLocked;
                newNode.districtID = censusGeometryNodeList[uogID].districtID;
                childBlockList.Add(newNode);
            }
            else
            {
                foreach (var childNode in censusGeometryNodeList[uogID].childNodeList)
                {
                    var childNodeList = GetCensusBlocksFromUogID(childNode.uogID);
                    childBlockList.AddRange(childNodeList);
                }
            }
            return childBlockList;
            
        }

        private void GetUnlockedUogDistricts(ref List<int> districtList, int uogID)
        {
            // The uog sub-shapes could be spread across multiple districts
            // if so, need to find the district for each of child shape
            if (censusGeometryNodeList[uogID].districtID == CensusGeometryConstants.multiDistrictID)
            {
                foreach (var childNode in censusGeometryNodeList[uogID].childNodeList)
                {
                    GetUnlockedUogDistricts(ref districtList, childNode.uogID);
                }
            }
            else
            {
                if (!censusGeometryNodeList[uogID].isLocked)
                {
                    districtList.Add(censusGeometryNodeList[uogID].districtID);
                }
            }
            return;
        }

        //public int GetAssignedDistrict(int uogID)
        //{
        //    if ((uogID < 1) & (uogID > censusGeometryNodeList.Count))
        //    {
        //        throw new Exception("Invalid UOGID [" + uogID.ToString() + "] detected in GetAssignedDistrict");
        //    }
        //    else
        //    {
        //        return censusGeometryNodeList[uogID].districtID;
        //    }
        //}

        public string GetDistrictFromRegionID(int layerID, int regionID)
        {
            string district = "xxx";

            var districtQuery = from planNode in censusGeometryNodeList
                                where planNode.layerID == layerID
                                & planNode.regionID == regionID
                                select planNode.districtID.ToString();
            if (districtQuery.Count() == 1)
            {
                district = districtQuery.First();
                if (district == "0") district = "UnAssigned";
            }

            return district;
        }

    }
}
